<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Snake Game - Enhanced</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: #111;
font-family: 'Arial', sans-serif;
touch-action: none;
}
canvas {
display: block;
}
#score {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 24px;
text-shadow: 0 0 5px #0ff;
z-index: 100;
}
#game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 48px;
text-align: center;
display: none;
text-shadow: 0 0 10px #f00;
z-index: 100;
}
#restart {
margin-top: 20px;
padding: 12px 24px;
font-size: 20px;
background: linear-gradient(45deg, #ff8a00, #e52e71);
color: white;
border: none;
border-radius: 25px;
cursor: pointer;
box-shadow: 0 0 15px rgba(229, 46, 113, 0.7);
transition: all 0.3s ease;
}
#restart:hover {
transform: scale(1.05);
box-shadow: 0 0 20px rgba(229, 46, 113, 0.9);
}
#controls {
position: absolute;
bottom: 20px;
left: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
z-index: 100;
}
</style>
</head>
<body>
<div id="score">Score: 0</div>
<div id="game-over">
Game Over!<br>
<button id="restart">Play Again</button>
</div>
<div id="controls">
Arrow Keys: X/Y movement | W/S: Z movement | Drag to rotate view
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// Enhanced 3D Snake Game with fixed controls
let score = 0;
let gameOver = false;
let snake = [];
let food = null;
let direction = { x: 1, y: 0, z: 0 };
let nextDirection = { x: 1, y: 0, z: 0 };
let lastUpdateTime = 0;
let gameSpeed = 150;
let speedIncreaseInterval = null;
// Scene setup with better visuals
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// Enhanced lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1);
directionalLight.castShadow = true;
scene.add(directionalLight);
const hemisphereLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 0.5);
scene.add(hemisphereLight);
// Camera position
camera.position.set(25, 25, 25);
camera.lookAt(0, 0, 0);
// Mouse controls for camera
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
document.addEventListener('mousedown', (e) => {
isDragging = true;
previousMousePosition = { x: e.clientX, y: e.clientY };
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const deltaMove = {
x: e.clientX - previousMousePosition.x,
y: e.clientY - previousMousePosition.y
};
// Rotate camera around the scene
camera.position.x -= deltaMove.x * 0.1;
camera.position.y += deltaMove.y * 0.1;
camera.lookAt(0, 0, 0);
previousMousePosition = { x: e.clientX, y: e.clientY };
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
// Touch controls for mobile
document.addEventListener('touchstart', (e) => {
isDragging = true;
previousMousePosition = { x: e.touches[0].clientX, y: e.touches[0].clientY };
e.preventDefault();
});
document.addEventListener('touchmove', (e) => {
if (!isDragging) return;
const deltaMove = {
x: e.touches[0].clientX - previousMousePosition.x,
y: e.touches[0].clientY - previousMousePosition.y
};
camera.position.x -= deltaMove.x * 0.1;
camera.position.y += deltaMove.y * 0.1;
camera.lookAt(0, 0, 0);
previousMousePosition = { x: e.touches[0].clientX, y: e.touches[0].clientY };
e.preventDefault();
});
document.addEventListener('touchend', () => {
isDragging = false;
});
// Larger game grid
const gridSize = 30;
// Create grid helper for better orientation
const gridHelper = new THREE.GridHelper(gridSize, gridSize, 0x555555, 0x333333);
scene.add(gridHelper);
// Create snake segment with better visuals
function createSnakeSegment(x, y, z, isHead = false) {
const geometry = new THREE.BoxGeometry(0.9, 0.9, 0.9);
const material = new THREE.MeshPhongMaterial({
color: isHead ? 0x00ffaa : 0x00cc88,
emissive: isHead ? 0x00aa77 : 0x008855,
emissiveIntensity: 0.2,
shininess: 30
});
const cube = new THREE.Mesh(geometry, material);
cube.position.set(x, y, z);
cube.castShadow = true;
cube.receiveShadow = true;
scene.add(cube);
return cube;
}
// Initialize snake with more segments
function initSnake() {
snake = [];
for (let i = 0; i < 5; i++) {
snake.push(createSnakeSegment(-i, 0, 0, i === 0));
}
}
initSnake();
// Create food with better visuals
function createFood() {
const x = Math.floor(Math.random() * (gridSize-2)) - Math.floor(gridSize / 2) + 1;
const y = Math.floor(Math.random() * (gridSize-2)) - Math.floor(gridSize / 2) + 1;
const z = Math.floor(Math.random() * (gridSize-2)) - Math.floor(gridSize / 2) + 1;
const geometry = new THREE.SphereGeometry(0.5, 32, 32);
const material = new THREE.MeshPhongMaterial({
color: 0xff5555,
emissive: 0xff0000,
emissiveIntensity: 0.3
});
const sphere = new THREE.Mesh(geometry, material);
sphere.position.set(x, y, z);
sphere.castShadow = true;
scene.add(sphere);
// Add pulsing animation
let scale = 1;
let pulseDirection = 0.02;
function animateFood() {
scale += pulseDirection;
if (scale > 1.2 || scale < 0.8) {
pulseDirection = -pulseDirection;
}
sphere.scale.set(scale, scale, scale);
if (!gameOver) requestAnimationFrame(animateFood);
}
animateFood();
return sphere;
}
// First food
food = createFood();
// Handle keyboard input (fixed controls)
const keyState = {};
document.addEventListener('keydown', (event) => {
keyState[event.key] = true;
// Prevent arrow keys from scrolling the page
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'w', 'W', 's', 'S'].includes(event.key)) {
event.preventDefault();
}
});
document.addEventListener('keyup', (event) => {
keyState[event.key] = false;
});
function updateDirection() {
// Only change direction if not moving in the opposite direction
if (keyState['ArrowUp'] && direction.y === 0 && direction.z === 0) {
nextDirection = { x: 0, y: 1, z: 0 };
}
if (keyState['ArrowDown'] && direction.y === 0 && direction.z === 0) {
nextDirection = { x: 0, y: -1, z: 0 };
}
if (keyState['ArrowLeft'] && direction.x === 0 && direction.z === 0) {
nextDirection = { x: -1, y: 0, z: 0 };
}
if (keyState['ArrowRight'] && direction.x === 0 && direction.z === 0) {
nextDirection = { x: 1, y: 0, z: 0 };
}
if ((keyState['w'] || keyState['W']) && direction.z === 0 && direction.y === 0) {
nextDirection = { x: 0, y: 0, z: 1 };
}
if ((keyState['s'] || keyState['S']) && direction.z === 0 && direction.y === 0) {
nextDirection = { x: 0, y: 0, z: -1 };
}
}
// Check collision with walls or self
function checkCollision(x, y, z) {
// Check walls (smaller boundary than grid for better playability)
const boundary = gridSize/2 - 1;
if (Math.abs(x) > boundary || Math.abs(y) > boundary || Math.abs(z) > boundary) {
return true;
}
// Check self (skip head)
for (let i = 1; i < snake.length; i++) {
if (Math.abs(snake[i].position.x - x) < 0.9 &&
Math.abs(snake[i].position.y - y) < 0.9 &&
Math.abs(snake[i].position.z - z) < 0.9) {
return true;
}
}
return false;
}
// Game loop
function animate(currentTime) {
requestAnimationFrame(animate);
// Update direction based on key state
updateDirection();
// Game update
if (!gameOver && (!lastUpdateTime || currentTime - lastUpdateTime > gameSpeed)) {
lastUpdateTime = currentTime;
direction = { ...nextDirection };
const head = snake[0];
const newX = head.position.x + direction.x;
const newY = head.position.y + direction.y;
const newZ = head.position.z + direction.z;
if (checkCollision(newX, newY, newZ)) {
gameOver = true;
document.getElementById('game-over').style.display = 'block';
return;
}
const newHead = createSnakeSegment(newX, newY, newZ, true);
snake.unshift(newHead);
// Make previous head no longer the head
if (snake.length > 1) {
snake[1].material.color.setHex(0x00cc88);
snake[1].material.emissive.setHex(0x008855);
}
if (Math.abs(newX - food.position.x) < 1 &&
Math.abs(newY - food.position.y) < 1 &&
Math.abs(newZ - food.position.z) < 1) {
score++;
document.getElementById('score').textContent = `Score: ${score}`;
scene.remove(food);
food = createFood();
// Increase speed every 5 points
if (score % 5 === 0) {
clearTimeout(speedIncreaseInterval);
gameSpeed = Math.max(50, gameSpeed - 10);
}
} else {
const tail = snake.pop();
scene.remove(tail);
}
}
renderer.render(scene, camera);
}
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Restart game (fixed)
document.getElementById('restart').addEventListener('click', () => {
// Clear snake and food
snake.forEach(segment => scene.remove(segment));
if (food) scene.remove(food);
// Reset game state
score = 0;
gameOver = false;
direction = { x: 1, y: 0, z: 0 };
nextDirection = { x: 1, y: 0, z: 0 };
lastUpdateTime = 0;
gameSpeed = 150;
// Recreate snake and food
initSnake();
food = createFood();
// Reset camera position
camera.position.set(25, 25, 25);
camera.lookAt(0, 0, 0);
// Update UI
document.getElementById('score').textContent = `Score: ${score}`;
document.getElementById('game-over').style.display = 'none';
});
// Start animation
animate();
</script>
</body>
</html>